import {_decorator, instantiate, Node, NodePool, Prefab, resources} from 'cc';

const {ccclass, property} = _decorator;

/**
 * 对象池管理器
 */
export class PoolManager {
    /**
     * 节点缓存
     * 1、单个任意节点
     * 2、一个预制体可能对应多个节点
     * 一个预制体可能会创建出好多个节点对象出来，所以缓存时，
     * 会出现一个预制体对应多个节点对象这种情况，所以pools的值对象是NodePool
     */
    private nodePools: Map<string, NodePool> = new Map<string, NodePool>();

    /**
     * 对象池管理器实例
     */
    private static INSTANCE: PoolManager;

    /**
     * 获取对象池管理器实例
     */
    public static getInstance(): PoolManager {
        // 当对象池管理器实例存在时
        if (this.INSTANCE) {
            // 直接返回对象池管理器实例
            return this.INSTANCE;
        }
        // 创建对象池管理器实例
        this.INSTANCE = new PoolManager();
        // 返回对象池管理器实例
        return this.INSTANCE;
    }

    public setData(path: string, parent: Node) {
        resources.load(path, Prefab, (err: Error, prefab: Prefab) => {

        });
    }

    /**
     * 从预制体池中获取指定预制体的节点
     *
     * @param prefab 预制体
     * @param parent 父节点
     */
    public createNodeByPrefab(prefab: Prefab, parent: Node) {
        // 预制体名称
        let prefabName = prefab.name;

        // 如果预制体在对象池中没有节点对象，那么就生成一个节点对象
        // 并存入对象池中
        let node: Node;
        // 当对象池中有此预制体时
        if (this.nodePools.has(prefabName)) {
            // 获取预制体对应的节点池
            let nodePool = this.nodePools.get(prefabName);
            // 当节点池的大小大于0时
            if (nodePool.size() > 0) {
                // 直接从节点池中获取
                node = nodePool.get();
            } else {
                // 根据预制体创建
                node = instantiate(prefab);
            }
        } else {
            // 对象池中没有这个预制体所对应的节点池
            // 创建一个节点池
            let nodePool = new NodePool();
            // 添加到缓存中
            this.nodePools.set(prefabName, nodePool);
            // 根据预制体创建一个节点
            node = instantiate(prefab);
        }

        // 设置父节点
        node.parent = parent;
        // 激活节点
        node.active = true;
        // 返回节点
        return node;
    }

    /**
     * 根据名称获取节点
     *
     * @param prefab 预制体
     * @param parent 父节点
     */
    public getNode(prefab: Prefab, parent: Node): Node {
        // 返回值
        let result: Node;

        let key = prefab.name;
        if (this.nodePools.has(key)) {
            let nodePool = this.nodePools.get(key);
            if (nodePool.size() > 0) {
                result = nodePool.get();
            } else {
                result = instantiate(prefab);
            }
        } else {
            this.nodePools.set(key, new NodePool());
            result = instantiate(prefab);
        }

        result.parent = parent;
        result.active = true;
        // 返回结果
        return result;
    }

    /**
     * 缓存节点
     *
     * @param node 需要缓存的节点
     */
    public putNode(node: Node) {
        // 获取节点名称
        let nodeName = node.name;
        // 节点池
        let nodePool: NodePool;
        // 当缓存中存在此节点名称时
        if (this.nodePools.has(nodeName)) {
            // 获取节点池
            nodePool = this.nodePools.get(nodeName);
        } else {
            // 当缓存中不存在此节点名称时
            // 创建新节点池
            nodePool = new NodePool();
            // 将节点池存入节点缓存中
            this.nodePools.set(nodeName, nodePool);
        }

        // 将节点存入节点池
        nodePool.put(node);
    }

    /**
     * 清除指定名称的节点池
     *
     * @param name 节点名称
     */
    public clearNodePool(name: string) {
        // 当缓存中存在此节点名称时
        if (this.nodePools.has(name)) {
            // 获取对应的节点池
            let nodePool = this.nodePools.get(name);
            // 清空节点池中所缓存的对象
            nodePool.clear();
        }
    }

    /**
     * 根据预制体预先生成节点池
     *
     * @param prefab 预制体
     * @param size 生成节点对象的数量
     */
    public preloadPool(prefab: Prefab, size: number) {
        // 获取预制体名称
        let key = prefab.name;
        // 缓存中的节点池
        let nodePool: NodePool;
        // 当缓存中已存在对应的key时
        if (this.nodePools.has(key)) {
            // 获取缓存中的节点池
            nodePool = this.nodePools.get(key);
        } else {
            // 创建新的节点池
            nodePool = new NodePool();
            // 将新节点池存入缓存中
            this.nodePools.set(key, nodePool);
        }

        // 循环创建节点对象
        for (let i = 0; i < size; i++) {
            // 根据预制体创建节点对象
            let node = instantiate(prefab);
            // 将创建好的节点对象存入节点池中
            nodePool.put(node);
        }
    }
}